package com.mall.seckil.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.mall.common.dto.SkuInfoDTO;
import com.mall.common.utils.Result;
import com.mall.common.vo.UserInfoVo;
import com.mall.common.dto.SeckillOrderDTO;
import com.mall.seckil.dto.SeckillSessionDTO;
import com.mall.seckil.dto.SeckillSkuRedisDto;
import com.mall.seckil.feign.CouponFeignClient;
import com.mall.seckil.feign.ProductFeignClient;
import com.mall.seckil.interceptor.SeckillIndexInterceptor;
import com.mall.seckil.service.SeckillService;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.BoundHashOperations;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

/**
 * @author: 谢君臣
 * @Date: 2022/9/18-16:15
 * @version: 1.0
 * @function:
 */
@Service
public class SeckillServiceImpl implements SeckillService {
    @Resource
    private CouponFeignClient couponFeignClient;
    @Resource
    private ProductFeignClient productFeignClient;
    @Autowired
    private StringRedisTemplate redisTemplate;
    @Resource
    private RedissonClient redissonClient;
    @Resource
    private RabbitTemplate rabbitTemplate;


    private final static String SESSION_PREFIX = "seckill:session:";
    private final static String SKUSECKILL_CACHE_PREFIX = "seckill:skus:";
    private final static String SKUSTOCK_PREFIX = "seckill:stock:"; // 商品随机码
    private final static String USERKILL_PREFIX = "seckill:user:"; // 秒杀成功的用户


    @Override
    public void uploadSeckillSkuLatest3Days() {
        Result<List<SeckillSessionDTO>> latest3DaySession = couponFeignClient.getLatest3DaySession();
        List<SeckillSessionDTO> sessionDTOList = latest3DaySession.getData();
        // 缓存活动
        saveSessionInfos(sessionDTOList);
    }

    @Override
    public List<SeckillSkuRedisDto> getCurrentSession() {
        // 获取场次
        List<SeckillSkuRedisDto> res = new ArrayList<>();
        long time = new Date().getTime();
        Set<String> keys = redisTemplate.keys(SESSION_PREFIX + "*");
        for (String key : keys) {
            String[] split = key.replace(SESSION_PREFIX, "").split("_");
            if (Long.parseLong(split[0]) <= time && Long.parseLong(split[1]) >= time) {
                List<String> list = redisTemplate.opsForList().range(key, -100, 100);
                BoundHashOperations<String, String, String> ops = redisTemplate.boundHashOps(SKUSECKILL_CACHE_PREFIX);
                List<String> objs = ops.multiGet(list);
                if (!objs.isEmpty()) {
                    List<SeckillSkuRedisDto> temp = objs.stream().map(item -> {
                        SeckillSkuRedisDto redisDto = JSON.parseObject(item, SeckillSkuRedisDto.class);
                        return redisDto;
                    }).collect(Collectors.toList());
                    res.addAll(temp);
                }
            }
        }
        return res;
    }

    @Override
    public SeckillSkuRedisDto getSkuSeckillInfo(Long skuId) {
        BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUSECKILL_CACHE_PREFIX);
        Set<String> keys = hashOps.keys();
        String regx = "\\d_" + skuId;
        for (String key : keys) {
            if (Pattern.matches(regx, key)) {
                SeckillSkuRedisDto skuRedisDto = JSON.parseObject(hashOps.get(key), SeckillSkuRedisDto.class);
                Long curr = new Date().getTime();
                if (curr >= skuRedisDto.getStartTime() && curr <= skuRedisDto.getEndTime()) {
                } else skuRedisDto.setRandomCode(null);
                return skuRedisDto;
            }
        }
        return null;
    }

    @Override
    public String killSku(String killId, String key, int num) throws RuntimeException {
        // 数据校验
        UserInfoVo userInfoVo = SeckillIndexInterceptor.threadLocal.get();
        try {
            BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(SKUSECKILL_CACHE_PREFIX);
            String jsonStr = hashOps.get(killId);
            SeckillSkuRedisDto redisDto = JSON.parseObject(jsonStr, SeckillSkuRedisDto.class);
            Long startTime = redisDto.getStartTime();
            Long endTime = redisDto.getEndTime();
            long curr = new Date().getTime();
            if (curr < startTime || curr > endTime)
                throw new RuntimeException("秒杀不在规定时间内");
            if (!redisDto.getRandomCode().equals(key))
                throw new RuntimeException("秒杀随机码不正确");
            if (redisDto.getSeckillLimit().compareTo(new BigDecimal(num)) != 0)
                throw new RuntimeException("秒杀数量超过限制");
            // 记录用户是否秒杀
            long ttl = endTime - curr;
            String userKillToken = userInfoVo.getId().toString() + "_" + redisDto.getPromotionSessionId().toString();
            Boolean ifAbsent = redisTemplate.opsForValue().setIfAbsent(USERKILL_PREFIX + userKillToken, String.valueOf(num), ttl, TimeUnit.MILLISECONDS);
            if (!ifAbsent)
                if (redisTemplate.opsForValue().get(USERKILL_PREFIX + userKillToken) != null)
                    throw new RuntimeException("恭喜UU已经抢购成功，但活动名额有限，给其他UU们一个机会吧。");
                else
                    throw new RuntimeException("手速慢了，没抢到……^_^……");
            RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_PREFIX + key);
            boolean acquire = semaphore.tryAcquire(num, 100, TimeUnit.MILLISECONDS);
            if (!acquire)
                throw new RuntimeException("秒杀失败");
            // 快速下单
            SeckillOrderDTO orderDTO = new SeckillOrderDTO();
            orderDTO.setMemberId(userInfoVo.getId());
            orderDTO.setMemberUserName(userInfoVo.getUsername());
            orderDTO.setOrderSn(IdWorker.getTimeId());
            orderDTO.setSkuId(redisDto.getSkuId());
            orderDTO.setPromotionSessionId(redisDto.getPromotionSessionId());
            orderDTO.setSeckillCount(new BigDecimal(num));
            orderDTO.setSeckillPrice(redisDto.getSeckillPrice());
            rabbitTemplate.convertAndSend("order-event-exchange", "order-seckill-order", orderDTO);
            return orderDTO.getOrderSn();
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    private void saveSessionSkuInfos(SeckillSessionDTO seckillSessionDTO) {
        BoundHashOperations<String, Object, Object> hashOps = redisTemplate.boundHashOps(SKUSECKILL_CACHE_PREFIX);
        seckillSessionDTO.getRelationDTOList().stream().forEach(item -> {
            // 添加防攻击码
            String token = UUID.randomUUID().toString().replace("-", "");
            if (Boolean.FALSE.equals(redisTemplate.hasKey(SKUSTOCK_PREFIX + token))) {
                SeckillSkuRedisDto seckillSkuRedisDto = new SeckillSkuRedisDto();
                BeanUtils.copyProperties(item, seckillSkuRedisDto);
                seckillSkuRedisDto.setStartTime(seckillSessionDTO.getStartTime().getTime());
                seckillSkuRedisDto.setEndTime(seckillSessionDTO.getEndTime().getTime());
                Result<SkuInfoDTO> infoDTOResult = productFeignClient.get(item.getSkuId());
                seckillSkuRedisDto.setRandomCode(token);
                // 设置秒杀信号量
                RSemaphore semaphore = redissonClient.getSemaphore(SKUSTOCK_PREFIX + token);
                semaphore.trySetPermits(item.getSeckillCount().intValue());
//                System.out.println(token);
                if (infoDTOResult.getCode() == 0) {
                    SkuInfoDTO skuInfoDTO = productFeignClient.get(item.getSkuId()).getData();
                    seckillSkuRedisDto.setSkuInfoDTO(skuInfoDTO);
                    hashOps.put(seckillSessionDTO.getId().toString() + "_" + item.getSkuId().toString(), JSON.toJSONString(seckillSkuRedisDto));
                }
            }
        });
    }

    private void saveSessionInfos(List<SeckillSessionDTO> sessionDTOList) {
        sessionDTOList.forEach(seckillSessionDTO -> {
            long startTime = seckillSessionDTO.getStartTime().getTime();
            long endTime = seckillSessionDTO.getEndTime().getTime();
            String key = SESSION_PREFIX + startTime + "_" + endTime;
            if (Boolean.FALSE.equals(redisTemplate.hasKey(key))) {  // 开始时间-结束时间
                List<String> collect = seckillSessionDTO.getRelationDTOList()
                        .stream()
                        .map(item -> seckillSessionDTO.getId().toString() + "_" + item.getSkuId().toString())
                        .collect(Collectors.toList());
                redisTemplate.opsForList().leftPushAll(key, collect);
                saveSessionSkuInfos(seckillSessionDTO);
            } else {
                List<String> list = redisTemplate.opsForList().range(key, 0, redisTemplate.opsForList().size(key));
                Set<String> set = list.stream().map(item -> item.split("_")[0]).collect(Collectors.toSet());
                String sessionId = seckillSessionDTO.getId().toString(); // 活动id
                if (!set.contains(sessionId)) { // 该活动没有处理
                    List<String> collect = seckillSessionDTO.getRelationDTOList()
                            .stream()
                            .map(item -> seckillSessionDTO.getId().toString() + "_" + item.getSkuId().toString())
                            .collect(Collectors.toList());
                    redisTemplate.opsForList().leftPushAll(key, collect);
                    saveSessionSkuInfos(seckillSessionDTO);
                }
            }
        });
    }
}
